﻿using OverStore.Exceptions;
using System;
using System.Collections.Generic;

namespace OverStore.Runtime.ObjectRepository
{
    /// <summary>
    /// Base interface for all object repositories, allows define 
    /// type of object served by repository and save all changes 
    /// of objects in repository.
    /// </summary>
    public interface IRepositoryBase : IRuntimeComponent
    {
        /// <summary>
        /// Get type of objects served by repository.
        /// </summary>
        Type Type { get; }

        /// <summary>
        /// Save changes made to all objects registered in repository.
        /// </summary>
        /// <exception cref="T:OverStore.Exceptions.PersistenceOperationIsNotSupportedException">Operation is not supported because of configuration part responsible for this operation is missing or does not support specific functionallity.</exception>
        void SaveAll();
    }

    /// <summary>
    /// Exposes set of methods for perform persisting operations on objects of <typeparamref name="TInstance"/> type. 
    /// Persistence operation includes object loading, saving, deleting, attaching etc.
    /// </summary>
    /// <typeparam name="TInstance">The type of the instance served by repositorys.</typeparam>
    public interface IRepository<TInstance> : IRepositoryBase
        where TInstance : class
    {
        /// <summary>
        ///     <para> Updates data of specified instance using data extracted from data storage, 
        ///     and reloads instance's aggregates and marks instance as unchanged, 
        ///     so further saving will not cause data storage updating until object will be changed.
        ///     Method returns <c>true</c> if instance was successfuly reloaded, <c>false</c> if
        ///     data storage commands used for reloading instance does not return any data.
        /// </para>
        ///     <para> If specified <paramref name="instance"/> is not registered in repository, method also registers it.
        /// If deffer loading is supported and enabled, the object will only be registered for
        /// loading, and loading itself will be performed by request.</para>
        /// <para>
        /// If repository is configured for deferred loading, method immediately returns <c>true</c>,
        /// and throws <see cref="PersistingRuntimeException"/> if reloading will fails.
        /// </para>
        /// </summary>
        /// <param name="instance">The instance to reload.</param>
        /// <exception cref="IdentityReferenceIntegrityException">Different instance with same  already registered.</exception>
        /// <exception cref="PersistingRuntimeException">Error is occured during reload object data.</exception>
        bool Reload(TInstance instance);

        /// <summary>
        ///     <para>
        /// Saves changes made in <paramref name="instance"/> in data storage and invokes saving instance's aggregates.
        /// If instance was previously marked as deleted, method removes it from data storage.
        /// </para>
        ///     <para>
        /// If instance supports deffer loading and is not loaded yet, method does not perform any
        /// actions, because changes can not be made without instance loading.
        /// </para>
        ///     <para>
        /// Method does not accept instance changes, but types implements current method should consider possibility
        /// of calling this method few times with same instance, in case of cycle references, for example.
        /// Repeated calls should not cause repeated saving, so method should take steps to avoid repeated saving.
        /// For example, instance may internally maintain list of saved instances, checks this list on saving and
        /// clear this list in see cref="IRepository{TInstance}.AcceptChanges" method.
        /// </para>
        /// </summary>
        /// <param name="instance">The instance to save changes.</param>
        /// <exception cref="PersistingRuntimeException">Error occured while save object.</exception>
        /// <exception cref="VersionConflictException">Version conflict occured during save object.</exception>
        /// <exception cref="IdentityReferenceIntegrityException">Different instance with same identifier already registered.</exception>
        /// <exception cref="PersistenceOperationIsNotSupportedException">Operation is not supported because of configuration part responsible for this operation is missing or does not support specific functionallity.</exception>
        void Save(TInstance instance);
        
        /// <summary>
        /// Mark <paramref name="instance"/> as deleted. 
        /// </summary>
        /// <remarks>
        /// Method itself does not save any changes to storage. For actually delete <paramref name="instance"/>
        /// from storage, call <see cref="IRepository{TInstance}.Save"/> method.
        /// </remarks>
        /// <param name="instance">The instance to mark as deleted.</param>
        /// <exception cref="IdentityReferenceIntegrityException">Different instance with same identifier already registered.</exception>
        /// <exception cref="PersistingRuntimeException">Error occured while mark object as deleted.</exception>
        /// <exception cref="PersistenceOperationIsNotSupportedException">Operation is not supported because of configuration part responsible for this operation is missing or does not support specific functionallity.</exception>
        void MarkAsDeleted(TInstance instance);

        /// <summary>
        /// Marks <paramref name="instance"/> as needed to be physically saved to storage even 
        /// if <paramref name="instance"/> data are not changed.
        /// This method may be called any times untill AcceptChanges method called, but instance will be saved only once.
        /// </summary>
        /// <remarks>
        /// Use this method in couple with optimistic locking (version checking) to organize object hierarchy into 'atom' group. 
        /// Call this method on successful instance saving using other members of 'atom' object group, then save these objects.
        /// </remarks>
        /// <param name="instance">Instance to mark as dirty.</param>
        /// <exception cref="IdentityReferenceIntegrityException">Different instance with the same identifier already registered.</exception>
        /// <exception cref="PersistingRuntimeException">Error occured while mark object as dirty.</exception>
        /// <exception cref="PersistenceOperationIsNotSupportedException">Operation is not supported because of configuration part responsible for this operation is missing or does not support specific functionallity.</exception>
        void MarkAsDirty(TInstance instance);

        /// <summary>
        /// <para> Registers <paramref name="instance"/> in repository, if it is not registered yet. After registration 
        /// <paramref name="instance"/> become available by its identifier. </para>
        /// <para> If same instance already registered in repository, method does not perform any action.</para>
        /// </summary>
        /// <param name="instance">The instance to registers in repository.</param>
        /// <exception cref="IdentityReferenceIntegrityException">Different instance with same identifier already registered.</exception>
        /// <exception cref="PersistingRuntimeException">Error occured during add object.</exception>
        /// <exception cref="PersistenceOperationIsNotSupportedException">Operation is not supported because of configuration part responsible for this operation is missing or does not support specific functionallity.</exception>
        void Add(TInstance instance);

        /// <summary>
        /// Joins specified instance or collection to repository if required, applies changes made to <paramref name="instance"/> and attach aggregates of specified instance.
        /// If specified instance is collection, attach each items and deletes items deleted from collection since detaching.
        /// </summary>
        /// <param name="instance">The instance to attach.</param>
        /// <returns>
        /// The instance registered in repository contains data of <paramref name="instance"/>.
        /// </returns>
        /// <exception cref="PersistingRuntimeException">Error occured during Attach object.</exception>
        /// <exception cref="PersistenceOperationIsNotSupportedException">Operation is not supported because of configuration part responsible for this operation is missing or does not support specific functionallity.</exception>
        TInstance Attach(TInstance instance);

        /// <summary>
        /// Return enumerator throught object sequence.
        /// </summary>
        /// <typeparam name="TParameter">The type of the parameter for command.</typeparam>
        /// <param name="parameter">The parameter for command.</param>
        /// <returns>
        /// The sequence of object read from data storage.
        /// </returns>
        /// <exception cref="PersistingRuntimeException">Error occured while use object reader.</exception>
        /// <exception cref="ParameterTypeNotSupportedException">Parameter with specified type is not supported.</exception>
        IEnumerable<TInstance> ExecuteObjectReader<TParameter>(TParameter parameter)
            where TParameter : class;

        /// <summary>
        /// Return enumerator throught object set.
        /// </summary>
        /// <returns>
        /// The sequence of object read from data storage.
        /// </returns>
        /// <exception cref="PersistingRuntimeException">Error occured while use object reader.</exception>
        IEnumerable<TInstance> ExecuteObjectReader();
    }
}